﻿using System;
using T0yK4T.IO;

namespace ToyChat.Server
{

    /// <summary>
    /// Default implementation of <see cref="ILoginModule"/>
    /// <para/>
    /// This module simply checks the database for matching informations
    /// </summary>
    public class LoginModule : ILoginModule
    {
        /// <summary>
        /// Attempts to authenticate the user information given in the specified <see cref="LoginChallenge"/>
        /// </summary>
        /// <param name="challengeResponse">The <see cref="LoginChallenge"/> to take userinformation from</param>
        /// <param name="dbHandler">The database handler to use for looking up user informations</param>
        /// <param name="info">Will contain a <see cref="IUserInfo"/> object that represents the user</param>
        /// <returns>True if succesful, otherwsie false</returns>
        private bool Authenticate(LoginChallenge challengeResponse, IDatabaseHandler dbHandler, out IUserInfo info)
        {
            info = dbHandler.FindUser(challengeResponse.Username.ToLower());
            if (info != null)
            {
                if (info.PassHash == dbHandler.GetPassHash(challengeResponse.Password))
                {
                    info.Nickname = challengeResponse.Nickname;
                    return true;
                }
                else
                    return false;
            }
            else
                return false;
        }

        /// <summary>
        /// Attempts to authenticate the specified <see cref="CryptoConnection"/>
        /// </summary>
        /// <param name="connection">The connection to authenticate</param>
        /// <param name="requireLogin">Specifies whether or not login is required to authenticate</param>
        /// <param name="dbHandler">The <see cref="IDatabaseHandler"/> to use for retrieving user informations</param>
        /// <param name="info">Will contain a <see cref="IUserInfo"/> object that represents the user that was authenticated (if succesful)</param>
        /// <returns>True if succesful, otherwise false</returns>
        public bool Authenticate(CryptoConnection connection, bool requireLogin, IDatabaseHandler dbHandler, out IUserInfo info)
        {
            LoginChallenge challenge = new LoginChallenge { PasswordRequired = requireLogin };
            Packet packet = new Packet { TypeID = (uint)InternalPacketTypes.LoginChallenge, Data = ToySerializer.Serialize(challenge) };
            connection.SendPacket(packet);
            int size;
            Packet received = connection.ReadPacket(5000, out size);
            info = null;
            if (received.TypeID == (uint)InternalPacketTypes.RegisterUser)
            {
                UserRegistrationPacket pkt = received.DeserializeData<UserRegistrationPacket>();
                return this.Register(pkt, dbHandler, out info);
            }
            else if (received.TypeID == (uint)InternalPacketTypes.LoginChallenge)
            {
                challenge = received.DeserializeData<LoginChallenge>();
                return this.Authenticate(challenge, dbHandler, out info);
            }
            return false;
        }

        /// <summary>
        /// Attempts to register a new user in the database using informations from the specified <see cref="UserRegistrationPacket"/>
        /// </summary>
        /// <param name="pkt">The <see cref="UserRegistrationPacket"/> that contains informations about the new user</param>
        /// <param name="newInfo">If succesful, this will be set to a new instance of <see cref="UserInfo"/> taken from the database</param>
        /// <param name="dbHandler">The <see cref="IDatabaseHandler"/> to use for creating the user</param>
        /// <returns>True if succesful, otherwise false</returns>
        public bool Register(UserRegistrationPacket pkt, IDatabaseHandler dbHandler, out IUserInfo newInfo)
        {
            IUserInfo info = dbHandler.FindUser(pkt.DesiredUsername);
            newInfo = null;
            if (info != null)
                return false;
            info = new UserInfo();
            info.Email = pkt.Email;
            info.PassHash = dbHandler.GetPassHash(pkt.DesiredPassword);
            info.Username = pkt.DesiredUsername.ToLower();
            info.Nickname = pkt.DesiredNick;
            dbHandler.StoreUser(info);
            newInfo = dbHandler.FindUser(info.Username);
            return newInfo != null;
        }
    }
}